#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
配置加载器
Configuration Loader

负责从YAML文件和环境变量加载配置
"""

import os
import logging
from pathlib import Path
from typing import Optional, Dict, Any

import yaml
from dotenv import load_dotenv

from .settings import (
    Settings,
    LoggingConfig,
    ModelConfig,
    CameraConfig,
    WebConfig,
    MCPConfig,
    GimbalConfig,
    TrackingConfig,
    DetectionConfig,
    SerialConfig,
    PidConfig,
    PidAxisConfig,
    KalmanFilterConfig,
    ServoTrackingConfig,
    TargetTrackingConfig,
)

logger = logging.getLogger("ConfigLoader")


def load_config(config_path: Optional[str] = None) -> Settings:
    """
    加载配置
    
    Args:
        config_path: 配置文件路径，如果为None则使用默认路径
        
    Returns:
        Settings: 配置对象
    """
    # 加载环境变量
    load_dotenv()
    
    # 确定配置文件路径
    if config_path is None:
        # 默认配置文件路径
        project_root = Path(__file__).parent.parent.parent
        config_path = project_root / "config" / "default.yaml"
    else:
        config_path = Path(config_path)
    
    # 加载YAML配置
    config_data = {}
    if config_path.exists():
        try:
            with open(config_path, 'r', encoding='utf-8') as f:
                config_data = yaml.safe_load(f) or {}
            logger.info(f"已加载配置文件: {config_path}")
        except Exception as e:
            logger.warning(f"加载配置文件失败: {e}")
            logger.info("将使用默认配置")
    else:
        logger.warning(f"配置文件不存在: {config_path}")
        logger.info("将使用默认配置")
    
    # 应用环境变量覆盖
    config_data = _apply_env_overrides(config_data)
    
    # 创建配置对象
    settings = _create_settings_from_dict(config_data)
    
    logger.info("配置加载完成")
    return settings


def _apply_env_overrides(config_data: Dict[str, Any]) -> Dict[str, Any]:
    """应用环境变量覆盖"""
    
    # Web服务配置
    if "web" not in config_data:
        config_data["web"] = {}
    
    if os.getenv("WEB_HOST"):
        config_data["web"]["host"] = os.getenv("WEB_HOST")
    if os.getenv("WEB_PORT"):
        config_data["web"]["port"] = int(os.getenv("WEB_PORT"))
    if os.getenv("WEB_DEBUG"):
        config_data["web"]["debug"] = os.getenv("WEB_DEBUG").lower() == "true"
    
    # MCP服务配置
    if "mcp" not in config_data:
        config_data["mcp"] = {}
    
    if os.getenv("MCP_HOST"):
        config_data["mcp"]["host"] = os.getenv("MCP_HOST")
    if os.getenv("MCP_PORT"):
        config_data["mcp"]["port"] = int(os.getenv("MCP_PORT"))
    if os.getenv("MCP_NAME"):
        config_data["mcp"]["name"] = os.getenv("MCP_NAME")
    
    # 摄像头配置
    if "camera" not in config_data:
        config_data["camera"] = {}
    
    if os.getenv("CAMERA_DEVICE"):
        config_data["camera"]["device_path"] = os.getenv("CAMERA_DEVICE")
    
    # 云台配置
    if "gimbal" not in config_data:
        config_data["gimbal"] = {}
    
    if os.getenv("GIMBAL_PORT"):
        config_data["gimbal"]["serial_port"] = os.getenv("GIMBAL_PORT")
    if os.getenv("GIMBAL_BAUDRATE"):
        config_data["gimbal"]["baudrate"] = int(os.getenv("GIMBAL_BAUDRATE"))
    
    return config_data


def _create_settings_from_dict(config_data: Dict[str, Any]) -> Settings:
    """从字典创建Settings对象"""
    
    # 创建子配置对象
    logging_config = LoggingConfig()
    if "logging" in config_data:
        logging_data = config_data["logging"]
        logging_config = LoggingConfig(
            file_level=logging_data.get("file_level", logging_config.file_level),
            console_level=logging_data.get("console_level", logging_config.console_level),
            format=logging_data.get("format", logging_config.format),
            datefmt=logging_data.get("datefmt", logging_config.datefmt),
            file_path=logging_data.get("file_path", logging_config.file_path),
            max_file_size=logging_data.get("max_file_size", logging_config.max_file_size),
            backup_count=logging_data.get("backup_count", logging_config.backup_count)
        )
    
    model_config = ModelConfig()
    if "model" in config_data:
        model_data = config_data["model"]
        model_config = ModelConfig(
            path=model_data.get("path", model_config.path),
            confidence_threshold=model_data.get("confidence_threshold", model_config.confidence_threshold),
            iou_threshold=model_data.get("iou_threshold", model_config.iou_threshold),
            input_image_size=model_data.get("input_image_size", model_config.input_image_size),
            dfl_bins=model_data.get("dfl_bins", model_config.dfl_bins),
            anchor_shapes=model_data.get("anchor_shapes", model_config.anchor_shapes),
        )
    
    camera_config = CameraConfig()
    if "camera" in config_data:
        camera_data = config_data["camera"]
        camera_config = CameraConfig(
            device_path=camera_data.get("device_path", camera_config.device_path),
            width=camera_data.get("width", camera_config.width),
            height=camera_data.get("height", camera_config.height),
            fps=camera_data.get("fps", camera_config.fps)
        )
    
    web_config = WebConfig()
    if "web" in config_data:
        web_data = config_data["web"]
        web_config = WebConfig(
            host=web_data.get("host", web_config.host),
            port=web_data.get("port", web_config.port),
            debug=web_data.get("debug", web_config.debug),
            threaded=web_data.get("threaded", web_config.threaded)
        )
    
    mcp_config = MCPConfig()
    if "mcp" in config_data:
        mcp_data = config_data["mcp"]
        mcp_config = MCPConfig(
            host=mcp_data.get("host", mcp_config.host),
            port=mcp_data.get("port", mcp_config.port),
            name=mcp_data.get("name", mcp_config.name),
            description=mcp_data.get("description", mcp_config.description)
        )
    
    gimbal_config = GimbalConfig()
    if "gimbal" in config_data:
        gimbal_data = config_data["gimbal"]
        gimbal_config = GimbalConfig(
            serial_port=gimbal_data.get("serial_port", gimbal_config.serial_port),
            baudrate=gimbal_data.get("baudrate", gimbal_config.baudrate),
            pan_pin=gimbal_data.get("pan_pin", gimbal_config.pan_pin),
            tilt_pin=gimbal_data.get("tilt_pin", gimbal_config.tilt_pin),
            init_pan_angle=gimbal_data.get("init_pan_angle", gimbal_config.init_pan_angle),
            init_tilt_angle=gimbal_data.get("init_tilt_angle", gimbal_config.init_tilt_angle),
            angle_range=gimbal_data.get("angle_range", gimbal_config.angle_range),
            supported_pins=gimbal_data.get("supported_pins", gimbal_config.supported_pins),
            min_pan=gimbal_data.get("min_pan", gimbal_config.min_pan),
            max_pan=gimbal_data.get("max_pan", gimbal_config.max_pan),
            min_tilt=gimbal_data.get("min_tilt", gimbal_config.min_tilt),
            max_tilt=gimbal_data.get("max_tilt", gimbal_config.max_tilt),
        )
    
    tracking_defaults = TrackingConfig()
    tracking_config = tracking_defaults
    if "tracking" in config_data:
        tracking_data = config_data["tracking"] or {}

        pid_data = tracking_data.get("pid", {})
        pid_pan_data = pid_data.get("pan", {})
        pid_tilt_data = pid_data.get("tilt", {})
        pid_config = PidConfig(
            pan=PidAxisConfig(
                kp=pid_pan_data.get("kp", tracking_defaults.pid.pan.kp),
                ki=pid_pan_data.get("ki", tracking_defaults.pid.pan.ki),
                kd=pid_pan_data.get("kd", tracking_defaults.pid.pan.kd),
                smoothing_factor=pid_pan_data.get("smoothing_factor", tracking_defaults.pid.pan.smoothing_factor),
                max_integral=pid_pan_data.get("max_integral", tracking_defaults.pid.pan.max_integral),
                first_update_factor=pid_pan_data.get("first_update_factor", tracking_defaults.pid.pan.first_update_factor),
                output_limit=pid_pan_data.get("output_limit", tracking_defaults.pid.pan.output_limit),
                integral_error_threshold=pid_pan_data.get("integral_error_threshold", tracking_defaults.pid.pan.integral_error_threshold),
            ),
            tilt=PidAxisConfig(
                kp=pid_tilt_data.get("kp", tracking_defaults.pid.tilt.kp),
                ki=pid_tilt_data.get("ki", tracking_defaults.pid.tilt.ki),
                kd=pid_tilt_data.get("kd", tracking_defaults.pid.tilt.kd),
                smoothing_factor=pid_tilt_data.get("smoothing_factor", tracking_defaults.pid.tilt.smoothing_factor),
                max_integral=pid_tilt_data.get("max_integral", tracking_defaults.pid.tilt.max_integral),
                first_update_factor=pid_tilt_data.get("first_update_factor", tracking_defaults.pid.tilt.first_update_factor),
                output_limit=pid_tilt_data.get("output_limit", tracking_defaults.pid.tilt.output_limit),
                integral_error_threshold=pid_tilt_data.get("integral_error_threshold", tracking_defaults.pid.tilt.integral_error_threshold),
            ),
        )

        kalman_data = tracking_data.get("kalman", {})
        kalman_config = KalmanFilterConfig(
            dt=kalman_data.get("dt", tracking_defaults.kalman.dt),
            process_noise_scale=kalman_data.get("process_noise_scale", tracking_defaults.kalman.process_noise_scale),
            measurement_noise_scale=kalman_data.get("measurement_noise_scale", tracking_defaults.kalman.measurement_noise_scale),
            prediction_time_factor=kalman_data.get("prediction_time_factor", tracking_defaults.kalman.prediction_time_factor),
            velocity_threshold=kalman_data.get("velocity_threshold", tracking_defaults.kalman.velocity_threshold),
            acceleration_enabled=kalman_data.get("acceleration_enabled", tracking_defaults.kalman.acceleration_enabled),
            adaptive_noise=kalman_data.get("adaptive_noise", tracking_defaults.kalman.adaptive_noise),
        )

        servo_tracking_data = tracking_data.get("servo_tracking", {})
        servo_tracking_config = ServoTrackingConfig(
            face_timeout=servo_tracking_data.get("face_timeout", tracking_defaults.servo_tracking.face_timeout),
            movement_threshold=servo_tracking_data.get("movement_threshold", tracking_defaults.servo_tracking.movement_threshold),
            angle_adjust_factor=servo_tracking_data.get("angle_adjust_factor", tracking_defaults.servo_tracking.angle_adjust_factor),
            min_servo_interval=servo_tracking_data.get("min_servo_interval", tracking_defaults.servo_tracking.min_servo_interval),
            distance_threshold=servo_tracking_data.get("distance_threshold", tracking_defaults.servo_tracking.distance_threshold),
            sleep_interval=servo_tracking_data.get("sleep_interval", tracking_defaults.servo_tracking.sleep_interval),
            first_detection_factor=servo_tracking_data.get("first_detection_factor", tracking_defaults.servo_tracking.first_detection_factor),
            max_angle_change=servo_tracking_data.get("max_angle_change", tracking_defaults.servo_tracking.max_angle_change),
            center_deadzone=servo_tracking_data.get("center_deadzone", tracking_defaults.servo_tracking.center_deadzone),
            prediction_enabled=servo_tracking_data.get("prediction_enabled", tracking_defaults.servo_tracking.prediction_enabled),
            prediction_time=servo_tracking_data.get("prediction_time", tracking_defaults.servo_tracking.prediction_time),
            prediction_weight=servo_tracking_data.get("prediction_weight", tracking_defaults.servo_tracking.prediction_weight),
            max_prediction_distance=servo_tracking_data.get("max_prediction_distance", tracking_defaults.servo_tracking.max_prediction_distance),
            angle_smoothing_factor=servo_tracking_data.get("angle_smoothing_factor", tracking_defaults.servo_tracking.angle_smoothing_factor),
        )

        target_tracking_data = tracking_data.get("target_tracking", {})
        target_tracking_config = TargetTrackingConfig(
            confidence_weight=target_tracking_data.get("confidence_weight", tracking_defaults.target_tracking.confidence_weight),
            size_weight=target_tracking_data.get("size_weight", tracking_defaults.target_tracking.size_weight),
            persistence_weight=target_tracking_data.get("persistence_weight", tracking_defaults.target_tracking.persistence_weight),
            position_weight=target_tracking_data.get("position_weight", tracking_defaults.target_tracking.position_weight),
            target_switch_threshold=target_tracking_data.get("target_switch_threshold", tracking_defaults.target_tracking.target_switch_threshold),
            target_timeout=target_tracking_data.get("target_timeout", tracking_defaults.target_tracking.target_timeout),
            max_history_count=target_tracking_data.get("max_history_count", tracking_defaults.target_tracking.max_history_count),
            max_position_distance=target_tracking_data.get("max_position_distance", tracking_defaults.target_tracking.max_position_distance),
            direction_threshold=target_tracking_data.get("direction_threshold", tracking_defaults.target_tracking.direction_threshold),
        )

        tracking_config = TrackingConfig(
            auto_enabled=tracking_data.get("auto_enabled", tracking_defaults.auto_enabled),
            kalman_enabled=tracking_data.get("kalman_enabled", tracking_defaults.kalman_enabled),
            prediction_enabled=tracking_data.get("prediction_enabled", tracking_defaults.prediction_enabled),
            target_center_threshold=tracking_data.get("target_center_threshold", tracking_defaults.target_center_threshold),
            pid=pid_config,
            kalman=kalman_config,
            servo_tracking=servo_tracking_config,
            target_tracking=target_tracking_config,
        )
    
    detection_config = DetectionConfig()
    if "detection" in config_data:
        detection_data = config_data["detection"]
        detection_config = DetectionConfig(
            class_names=detection_data.get("class_names", detection_config.class_names),
            colors=detection_data.get("colors", detection_config.colors),
            font_scale=detection_data.get("font_scale", detection_config.font_scale),
            line_thickness=detection_data.get("line_thickness", detection_config.line_thickness)
        )

    serial_config = SerialConfig()
    if "serial" in config_data:
        serial_data = config_data["serial"] or {}
        serial_config = SerialConfig(
            port=serial_data.get("port", serial_config.port),
            baudrate=serial_data.get("baudrate", serial_config.baudrate),
        )
    
    # 创建主配置对象
    settings = Settings(
        app_name=config_data.get("app_name", "智能人脸追踪系统"),
        version=config_data.get("version", "2.0.0"),
        environment=config_data.get("environment", "development"),
        logging=logging_config,
        model=model_config,
        camera=camera_config,
        web=web_config,
        mcp=mcp_config,
        gimbal=gimbal_config,
        tracking=tracking_config,
        detection=detection_config,
        serial=serial_config,
    )
    
    return settings
